import { Injectable, Logger } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { FileChunk } from 'src/database/entities/fileChunk.entity'
import { File } from 'src/database/entities/file.entity'
import { Repository } from 'typeorm'
import * as fs from 'fs-extra'
import * as path from 'path'
import * as mime from 'mime-types'

@Injectable()
export class UploadService {
  private readonly logger = new Logger(UploadService.name)
  private readonly uploadDir = path.join(process.cwd(), 'uploads')
  private readonly tempDir = path.join(this.uploadDir, 'temp')
  private readonly finalDir = path.join(this.uploadDir, 'final')

  constructor(
    @InjectRepository(FileChunk)
    private readonly fileChunkRepository: Repository<FileChunk>,
    @InjectRepository(File)
    private readonly fileRepository: Repository<File>
  ) {
    // 确保目录存在
    fs.ensureDirSync(this.tempDir)
    fs.ensureDirSync(this.finalDir)
  }

  // 获取用户文件目录
  private getUserFinalDir(userId: number): string {
    const userDir = path.join(this.finalDir, userId.toString())
    fs.ensureDirSync(userDir) // 确保用户目录存在
    return userDir
  }

  // 获取用户临时目录
  private getUserTempDir(userId: number): string {
    const userTempDir = path.join(this.tempDir, userId.toString())
    fs.ensureDirSync(userTempDir) // 确保用户临时目录存在
    return userTempDir
  }

  /**
   * 上传文件分片
   * @param file 文件对象
   * @param body 请求体
   * @param userId 用户ID
   */
  async uploadChunk(
    file: Express.Multer.File,
    body: any,
    userId: number
  ): Promise<any> {
    const { fileHash, chunkIndex, chunkHash, filename, totalChunks } = body

    // 使用用户特定的临时目录
    const userTempDir = this.getUserTempDir(userId)
    const chunkPath = path.join(userTempDir, `${fileHash}-${chunkIndex}`)

    try {
      // 保存分片到临时目录
      await fs.writeFile(chunkPath, file.buffer)

      // 记录分片信息到数据库
      let fileChunk = await this.fileChunkRepository.findOne({
        where: { fileHash, chunkIndex, userId }
      })

      if (!fileChunk) {
        fileChunk = this.fileChunkRepository.create({
          fileHash,
          chunkIndex: Number(chunkIndex),
          chunkHash,
          filename,
          chunkPath,
          totalChunks: Number(totalChunks),
          size: file.size,
          userId,
          uploaded: true
        })
      } else {
        fileChunk.uploaded = true
        fileChunk.chunkPath = chunkPath // 更新路径以防变更
      }

      await this.fileChunkRepository.save(fileChunk)
    } catch (error) {
      this.logger.error(`Error uploading chunk: ${error.message}`)
      throw error
    }
  }

  /**
   * 合并上传的文件分片
   * @param fileHash - 文件的唯一哈希值
   * @param filename - 最终文件的文件名
   * @param totalChunks - 文件分片的总数量
   * @param userId - 上传文件的用户ID
   */
  async mergeChunks(
    fileHash: string,
    filename: string,
    totalChunks: number,
    parentId: number,
    userId: number
  ): Promise<any> {
    try {
      this.logger.log(`开始合并文件: ${filename}, hash: ${fileHash}`)

      // 使用用户特定的文件目录
      const userFinalDir = this.getUserFinalDir(userId)
      const userTempDir = this.getUserTempDir(userId)
      const filePath = path.join(userFinalDir, filename)
      const writeStream = fs.createWriteStream(filePath)

      // 按顺序合并分片
      for (let i = 0; i < totalChunks; i++) {
        const chunkPath = path.join(userTempDir, `${fileHash}-${i}`)
        if (await fs.pathExists(chunkPath)) {
          const chunkBuffer = await fs.readFile(chunkPath)
          writeStream.write(chunkBuffer)

          await fs.unlink(chunkPath)
        } else {
          throw new Error(`分片 ${i} 不存在`)
        }
      }

      writeStream.end()

      await new Promise<void>((resolve, reject) => {
        writeStream.on('finish', resolve)
        writeStream.on('error', reject)
      })

      this.logger.log(`文件合并完成: ${filename}`)

      // 查找所有相关的分片记录
      const chunks = await this.fileChunkRepository.find({
        where: { fileHash, userId }
      })

      // 从数据库中删除分片记录
      if (chunks.length > 0) {
        await this.fileChunkRepository.remove(chunks)
        this.logger.log(`删除数据库分片记录: ${chunks.length} 条`)
      }

      // 创建文件记录
      let file = await this.fileRepository.findOne({
        where: { fileHash, userId }
      })

      // 获取文件信息
      const stats = await fs.stat(filePath)
      const mimeType = mime.lookup(filename) || 'application/octet-stream'

      if (!file) {
        file = this.fileRepository.create({
          filename,
          fileHash,
          filePath,
          size: stats.size,
          userId,
          mimeType,
          parentId
        })
      } else {
        file.filename = filename
        file.filePath = filePath
        file.size = stats.size
        file.mimeType = mimeType
        file.parentId = parentId !== undefined ? parentId : file.parentId
      }

      await this.fileRepository.save(file)
    } catch (error) {
      this.logger.error(`合并文件失败: ${error.message}`)
      throw error
    }
  }

  /**
   * 获取已上传的分片信息（用于断点续传）
   * @param fileHash 文件哈希
   * @param userId 用户ID
   */
  async getUploadedChunks(fileHash: string, userId: number): Promise<number[]> {
    try {
      const chunks = await this.fileChunkRepository.find({
        where: { fileHash, userId, uploaded: true },
        select: ['chunkIndex']
      })

      return chunks.map((chunk) => chunk.chunkIndex)
    } catch (error) {
      this.logger.error(`获取已上传分片失败: ${error.message}`)
      throw error
    }
  }

  /**
   * 检查文件是否已存在（用于秒传）
   * @param fileHash 文件哈希
   * @param userId 用户ID
   */
  async checkFileExists(
    fileHash: string,
    userId: number
  ): Promise<{ exists: boolean; filePath?: string }> {
    try {
      // 检查数据库中是否有该用户已合并的文件记录
      const file = await this.fileRepository.findOne({
        where: { fileHash, userId }
      })

      if (file) {
        // 检查文件是否实际存在
        if (await fs.pathExists(file.filePath)) {
          return { exists: true, filePath: file.filePath }
        }
      }

      return { exists: false }
    } catch (error) {
      this.logger.error(`检查文件是否存在失败: ${error.message}`)
      throw error
    }
  }

  /**
   * 取消文件上传，删除已上传的分片
   * @param fileHash 文件哈希
   * @param userId 用户ID
   */
  async cancelUpload(
    fileHash: string,
    userId: number
  ): Promise<{ success: boolean }> {
    try {
      this.logger.log(`取消上传文件: hash: ${fileHash}, userId: ${userId}`)

      // 获取用户临时目录
      const userTempDir = this.getUserTempDir(userId)

      // 找到所有相关的分片记录
      const chunks = await this.fileChunkRepository.find({
        where: { fileHash, userId }
      })

      // 删除所有临时分片文件
      for (const chunk of chunks) {
        const chunkPath = path.join(
          userTempDir,
          `${fileHash}-${chunk.chunkIndex}`
        )
        if (await fs.pathExists(chunkPath)) {
          await fs.unlink(chunkPath)
          this.logger.log(`删除分片文件: ${chunkPath}`)
        }
      }

      // 从数据库中删除分片记录
      if (chunks.length > 0) {
        await this.fileChunkRepository.remove(chunks)
        this.logger.log(`删除数据库分片记录: ${chunks.length} 条`)
      }

      return { success: true }
    } catch (error) {
      this.logger.error(`取消上传失败: ${error.message}`)
      throw error
    }
  }
}
